package com.export.service;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.export.db.dbutils.UniqueID;
import com.export.entity.DownloadLog;
import com.export.entity.ReportExportLog;
import common.statusenum.ExportLogEnum;
import common.util.DateTimeFormatterUtil;
import common.util.http.UploadFileResponseData;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * 报表
 * R 报表列表返回对象
 * D 报表详情列表返回对象
 * P 请求参数
 *
 * @author yuanqinglong
 * @since 2021/7/21 16:48
 */
public interface IReportService<P> extends IExportService {


    /**
     * 每次查询导出语句限制大小
     */
    int LIMIT_SIZES = 5000;

    /**
     * EXCEL每个工作表最大写入行数
     */
    int SHEET_SIZE = 100000;


    Object SYNC_WRITER = new Object();


    /**
     * 执行导出、下载方法
     *
     * @param p          p
     * @param fileName   文件名称
     * @param operatorId 操作符id
     */
    default void performExport(P p, String fileName, String operatorId) {
//        CompletableFuture.runAsync(() -> asyncExport(p, fileName, operatorId));
        asyncExport(p, fileName, operatorId);
    }


    /**
     * 异步导出
     *
     * @param p          p
     * @param fileName   文件名称
     * @param operatorId 操作符id
     */
    @Transactional(readOnly = true)
    default void asyncExport(P p, String fileName, String operatorId) {
        OutputStream outputStream = new ByteArrayOutputStream();
        ExcelWriter excelWriter = EasyExcelFactory.write(outputStream).build();
        fileName += DateTimeFormatterUtil.dateTimeToSimpleString(LocalDateTime.now()) + ".xlsx";
        // 保存日志
        ReportExportLog reportExportLog = new ReportExportLog(String.valueOf(UniqueID.generateId()), null,
                fileName, ExportLogEnum.BUILDING.getCode(), LocalDateTime.now(), LocalDateTime.now(), operatorId);
        try {
            this.saveExportLog(reportExportLog);
            int totalStatistics = this.downloadLogCount(p);
            // 循环查询次数 = 记录总数 / 每次查询行数
            int cycles = totalStatistics > LIMIT_SIZES ? totalStatistics / LIMIT_SIZES : 1;
            // 判断总数是否能整除每次查询的行数  不能整除将查询次数加 1
            cycles = (totalStatistics % LIMIT_SIZES) == 0 ? cycles : cycles + 1;
            long start1 = System.currentTimeMillis();
            for (int i = 1; i <= cycles; i++) {
                long start = System.currentTimeMillis();
                // 计算工作表sheet号 序号从1开始  每个sheet 十万行
                int sheetNum = (((i - 1) * LIMIT_SIZES) / SHEET_SIZE) + 1;
                List<DownloadLog> downloadLogs = this.downloadLog(p, i);
                WriteSheet writeSheet = EasyExcelFactory.writerSheet(sheetNum).head(DownloadLog.class).build();
                excelWriter.write(downloadLogs, writeSheet);
                System.err.println("循环第 " + i + " 次 用时：" + (System.currentTimeMillis() - start));
            }
            // 同步写入excel 上传excel
            this.syncWriterFinish(outputStream, excelWriter, fileName, reportExportLog);
                System.err.println("循总耗时：" + (System.currentTimeMillis() - start1));

        } catch (Exception e) {
            reportExportLog.setFileCreateStatus(ExportLogEnum.FAILED_TO_BUILD.getCode());
            this.updateExportLogStatus(reportExportLog);
            logger.error("导出 {}失败：", fileName, e);
        }
    }


    /**
     * 同步写入
     *
     * @param excelWriter  优秀的作家
     * @param outputStream 输出流
     * @param fileName     文件名称
     */
    default void syncWriterFinish(OutputStream outputStream, ExcelWriter excelWriter, String fileName, ReportExportLog reportExportLog) throws IOException {
        synchronized (SYNC_WRITER) {
            excelWriter.finish();
            UploadFileResponseData fileResponseData = this.uploadToFileService(outputStream, fileName);
            reportExportLog.setFileCreateStatus(ExportLogEnum.GENERATED.getCode());
            reportExportLog.setExportFileId(fileResponseData.getFileId());
            // 上传成功更新日志状态
            this.updateExportLogStatus(reportExportLog);
        }
    }


    /**
     * 导出报表
     *
     * @param p p
     */
    void exportReportTable(P p);


    /**
     * 导出详情列表
     *
     * @param p p
     */
    void exportDetailTable(P p);



    /**
     * 下载日志
     *
     * @param cycleIndex 循环次数
     * @param p          p
     * @return {@link List<DownloadLog>}
     */
    List<DownloadLog> downloadLog(P p, int cycleIndex);


    /**
     * 下载日志数
     *
     * @param p p
     * @return int
     */
    int downloadLogCount(P p);

}
